package teste;



import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.ArrayType;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.TypeDeclaration;

import br.pucrio.inf.les.genarch.ui.wizards.newGenArchJavaAspectJProfile;


/**
 * Find type referenced in a given CompilationUnit.
 * 
 * @author chrisl
 * @see org.eclipse.jdt.internal.corext.codemanipulation
 */
public class TypeReferenceASTVisitor extends ASTVisitor {

  ////////////////////////////////////////////////////////////////////////

  private static final String NAME = "TypeReferenceASTVisitor";
  private static final boolean DEBUG = false;
  
  private List<ITypeBinding> superClass = new ArrayList<ITypeBinding>();
  private List<ITypeBinding> references = new ArrayList<ITypeBinding>();
  private List<ITypeBinding> superInterfaces = new ArrayList<ITypeBinding>();
  private List<ITypeBinding> declarations = new ArrayList<ITypeBinding>();

  public static class Result {
    public static final int NONE = 0;
    public static final int CLASS = 1;
    public static final int INTERFACE = 2;
    public int fKind;
    /** Super classes.  Fully qualified typename -> ITypeBinding.*/
    public Map fSuperClasses;
    /** FullQualifiedName->ITypeBinding mapping.*/
    public Map fSuperInterfaces;
    /** Referenced types. FullyQualifiedName->ITypeBinding mapping.*/
    public Map fReferences;
    /** Declared types. FullyQualifiedName->ITypeBinding mapping.*/
    public Map fDeclares;

    public Result() {
      fKind = 0;
      fReferences = new HashMap();
      fSuperClasses = new HashMap();
      fSuperInterfaces = new HashMap();
      fDeclares = new HashMap();
    }
    public int getKind() {
      return fKind;
    }
    public Map getDeclares() {
      return fDeclares;
    }
    public Map getSuperClasses() {
      return fSuperClasses;
    }
    public Map getReferences() {
      return fReferences;
    }
    public Map getSuperInterfaces() {
      return fSuperInterfaces;
    }
  }
  private Result fResult;


  /**
   * @return Type references in a (String fullTypeName) -> (ITypeBinding type) hash.
   */
  public static TypeReferenceASTVisitor startAt(ASTNode root) {
    TypeReferenceASTVisitor visitor = new TypeReferenceASTVisitor();
    root.accept(visitor);
    return visitor;
  }

  ////////////////////////////////////////////////////////////////////////

  public Result getResult() {
    return fResult;
  }

  public TypeReferenceASTVisitor() {
    fResult = new Result();
  }

  private ITypeBinding addDeclaredType(Name node) {
    ITypeBinding type = resolveType(node);
    if (type != null){
    	declarations.add(type);
    	fResult.fDeclares.put(type.getPackage().getName() + "." + type.getName(), type);
    }
    return type;
  }

  private ITypeBinding addReference(ITypeBinding type) {
	  references.add(type);
    return addType(fResult.fReferences, type);
  }

  private ITypeBinding addSuperClass(ITypeBinding type) {
    if (type.isInterface()){
    	superClass.add(type);
      return addSuperInterface(type);
    }
    return addType(fResult.fSuperClasses, type);
  }

  private ITypeBinding addSuperInterface(ITypeBinding type) {
	  superInterfaces.add(type);
    return addType(fResult.fSuperInterfaces, type);
  }

  private ITypeBinding addType(Map table, ITypeBinding type) {
    if (type == null)
      return null;
    //    StringBuffer buf = new StringBuffer();
    //    buf.append(type.qualifiedPackageName());
    //    buf.append(".");
    //    buf.append(type.qualifiedSourceName());
    //    String typename = buf.toString();
    String typename = type.getPackage().getName();
    typename += "." + type.getName();
    if (DEBUG)
      System.err.println(NAME + ".addReference(): " + typename);
    if (!table.containsKey(typename)) {
      table.put(typename, type);
    }
    return type;
  }

  private ITypeBinding resolveType(Name node) {
    if (node != null) {
      while (node.isQualifiedName()) {
        node = ((QualifiedName) node).getQualifier();
      }
      if (DEBUG)
        System.err.println(NAME + ".resolveType(): " + node);
      IBinding binding = node.resolveBinding();
      if (binding != null && binding.getKind() == IBinding.TYPE) {
        return (ITypeBinding) binding;
      }
    }
    return null;
  }

  private ITypeBinding typeRefFound(Name node) {
    if (node != null) {
      while (node.isQualifiedName()) {
        node = ((QualifiedName) node).getQualifier();
      }
      if (DEBUG)
        System.err.println(NAME + ".typeRefFound(): " + node);
      IBinding binding = node.resolveBinding();
      if (binding != null && binding.getKind() == IBinding.TYPE) {
        addReference((ITypeBinding) binding);
        return (ITypeBinding) binding;
      }
    }
    return null;
  }

  private void possibleTypeRefFound(Name node) {
    while (node.isQualifiedName()) {
      node = ((QualifiedName) node).getQualifier();
    }
    if (DEBUG)
      System.err.println(NAME + ".possibleTypeRefFound(): " + node);
    IBinding binding = node.resolveBinding();
    if (binding != null && binding.getKind() == IBinding.TYPE)
      addReference((ITypeBinding) binding);
    //    if (binding == null || binding.getKind() == IBinding.TYPE) {
    //      // if the binding is null, we cannot determine if 
    //      // we have a type binding or not, so we will assume
    //      // we do.
    //      addReference((SimpleName) node);
    //    }
  }

  private void visitChildren(List elements) {
    int nElements = elements.size();
    for (int i = 0; i < nElements; i++) {
      ((ASTNode) elements.get(i)).accept(this);
    }
  }

  ////////////////////////////////////////////////////////////////////////

  /*
   * @see ASTVisitor#visit(ArrayType)
   */
  public boolean visit(ArrayType node) {
    node.getElementType().accept(this);
    return false;
  }

  /*
   * @see ASTVisitor#visit(SimpleType)
   */
  public boolean visit(SimpleType node) {
    typeRefFound(node.getName());
    return false;
  }

  /*
   * @see ASTVisitor#visit(QualifiedName)
   */
  public boolean visit(QualifiedName node) {
    possibleTypeRefFound(node); // possible ref
    return false;
  }

  /*
   * @see ASTVisitor#visit(PackageDeclaration)
   */
  public boolean visit(PackageDeclaration node) {
    return false;
  }

  /*
   * @see ASTVisitor#visit(ThisExpression)
   */
  public boolean visit(ThisExpression node) {
    typeRefFound(node.getQualifier());
    return false;
  }

  private void evalQualifyingExpression(Expression expr) {
    if (expr != null) {
      if (expr instanceof Name) {
        possibleTypeRefFound((Name) expr);
      } else {
        expr.accept(this);
      }
    }
  }

  /*
   * @see ASTVisitor#visit(ClassInstanceCreation)
   */
  public boolean visit(ClassInstanceCreation node) {
    typeRefFound(node.getName());
    evalQualifyingExpression(node.getExpression());
    if (node.getAnonymousClassDeclaration() != null) {
      node.getAnonymousClassDeclaration().accept(this);
    }
    visitChildren(node.arguments());
    return false;
  }

  /*
   * @see ASTVisitor#endVisit(MethodInvocation)
   */
  public boolean visit(MethodInvocation node) {
    evalQualifyingExpression(node.getExpression());
    visitChildren(node.arguments());
    return false;
  }

  /*
   * @see ASTVisitor#visit(SuperConstructorInvocation)
   */
  public boolean visit(SuperConstructorInvocation node) {
    evalQualifyingExpression(node.getExpression());
    visitChildren(node.arguments());
    return false;
  }

  /*
   * @see ASTVisitor#visit(FieldAccess)
   */
  public boolean visit(FieldAccess node) {
    evalQualifyingExpression(node.getExpression());
    return false;
  }

  /*
   * @see ASTVisitor#visit(SimpleName)
   */
  public boolean visit(SimpleName node) {
    // if the call gets here, it can only be a variable reference
    return false;
  }

  /*
   * @see ASTVisitor#visit(TypeDeclaration)
   */
  public boolean visit(TypeDeclaration node) {
	  node.getProperty(TypeDeclaration.INTERFACE_PROPERTY.toString());
	  node.getSuperclass();
	  node.superInterfaces();
//	  node.superInterfaceTypes();
    ITypeBinding type = typeRefFound(node.getSuperclass());
    if (type != null)
      addSuperClass(type);
    Iterator iter = node.superInterfaces().iterator();
    while (iter.hasNext()) {
      type = typeRefFound((Name) iter.next());
      if (type != null)
        addSuperInterface(type);
    }
    visitChildren(node.bodyDeclarations());
    type = addDeclaredType(node.getName());
    if (type != null) {
      if (type.isInterface())
        fResult.fKind |= Result.INTERFACE;
      if (type.isClass())
        fResult.fKind |= Result.CLASS;
    } else {
      System.out.println(NAME+".visit(TypeDeclaration): declared type==null: node="+node.getName());
    }
    return false;
  }

 

  /*
   * @see ASTVisitor#preVisit(ASTNode)
   */
  public void preVisit(ASTNode node) {
    if ((node.getFlags() & ASTNode.MALFORMED) != 0) {
    	System.out.println("ERRO");
    }
  }

	public List<ITypeBinding> getSuperClass() {
		return superClass;
	}
	
	public void setSuperClass(List<ITypeBinding> superClass) {
		this.superClass = superClass;
	}
	
	public List<ITypeBinding> getReferences() {
		return references;
	}
	
	public void setReferences(List<ITypeBinding> references) {
		this.references = references;
	}
	
	public List<ITypeBinding> getSuperInterfaces() {
		return superInterfaces;
	}
	
	public void setSuperInterfaces(List<ITypeBinding> superInterfaces) {
		this.superInterfaces = superInterfaces;
	}

	public void setDeclarations(List<ITypeBinding> declarations) {
		this.declarations = declarations;
	}

	public List<ITypeBinding> getDeclarations() {
		return declarations;
	}

}
